﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Channels;
using System.ServiceModel.Security.Tokens;
using System.ServiceModel;
using PSE.Framework.Configuration;
using PSE.Framework.Common.Security.Services.Authentication.ClientCredentials;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel.Description;
using System.Xml;
using System.IdentityModel.Tokens;
using System.IO;
using PSE.Framework.Common.Security.Services.Configuration;

namespace PSE.Framework.Common.Security.Services
{
    /// <summary>
    /// Classe para auxiliar a criação dos Bindings de serviço.
    /// </summary>
    public class BindingHelper
    {
        //constantes para identificacao de protocolo
        private const string HttpProtocol = "Http";
        private const string TcpProtocol = "Tcp";
        private const string NamedPipeProtocol = "NamedPipe";

        private const string SERVICES_CONFIGURATION_SECTION_NAME = "servicesConfiguration";

        private const string CERTIFICATE_CONFIGURATION_SECTION_NAME = "certificateConfiguration";

        /// <summary>
        /// Recuperar o protocolo configurado para o endpoint
        /// </summary>
        /// <param name="serviceEndpoint"></param>
        /// <returns></returns>
        public static string ResolveProtocol(ServiceEndpoint serviceEndpoint)
        {
            return ResolveProtocol(serviceEndpoint.Address);
        }

        /// <summary>
        /// Recuperar o protocolo configurado para o endpoint
        /// </summary>
        /// <param name="endpoint"></param>
        /// <returns></returns>
        public static string ResolveProtocol(EndpointAddress endpoint)
        {
            if (endpoint != null && endpoint.Uri != null)
            {
                string schema = endpoint.Uri.Scheme;

                switch (schema.ToUpper())
                {
                    case "NET.TCP":
                        return TcpProtocol;
                    case "NET.PIPE":
                        return NamedPipeProtocol;
                    case "HTTP":
                        return HttpProtocol;
                }
            }

            return string.Empty;
        }

        /// <summary>
        /// Selector para criação do Binding.
        /// </summary>
        /// <param name="protocol">Nome do protocolo</param>
        /// <returns>Binding com transporte sob o módulo especificado</returns>
        public static Binding CreateBinding(string protocol)
        {
            switch (protocol)
            {
                case HttpProtocol: return CreateHttpTransportBvmfBinding();
                case TcpProtocol: return CreateTcpTransportBvmfBinding();
                case NamedPipeProtocol: return CreateNamedPipeTransportBvmfBinding();
                default:
                    throw new ArgumentException("Invalid protocol {" + protocol + "}");
            }
        }

        internal static EndpointConfigurationElement RetrieveEndpointConfiguration(string contractName)
        {
            ConfigurationCollection<EndpointConfigurationElement> collection = ApplicationConfiguration.GetCurrent().GetSection<EndpointConfigurationSection>("endpointConfiguration").Endpoints;
            foreach (EndpointConfigurationElement element in collection)
            {
                if (element.Contract == contractName)
                    return element;
            }

            throw new Exception(string.Format("The contract '{0}' don't have a valid configuration.", contractName));
        }

        #region Service Transport

        /// <summary>
        /// Cria um Binding sob o protocolo Http para o Bvmf
        /// </summary>
        /// <returns>CustomBinding</returns>
        public static Binding CreateHttpTransportBvmfBinding()
        {
            //TODO: Incluir o Issuer Http
            HttpTransportBindingElement httpTransport = new HttpTransportBindingElement();

            httpTransport.MaxBufferPoolSize = 122880;
            httpTransport.MaxBufferSize = Int32.MaxValue;
            httpTransport.MaxReceivedMessageSize = Int32.MaxValue;

            SymmetricSecurityBindingElement messageSecurity = new SymmetricSecurityBindingElement();

            //TODO: Duvida - precisa passar um parameter ?
            //messageSecurity.EndpointSupportingTokenParameters.SignedEncrypted.Add(new BvmfTokenParameters());
            messageSecurity.EndpointSupportingTokenParameters.SignedEncrypted.Add(new TokenParameters());
            X509SecurityTokenParameters x509ProtectionParameters = new X509SecurityTokenParameters();
            x509ProtectionParameters.InclusionMode = SecurityTokenInclusionMode.Never;
            messageSecurity.ProtectionTokenParameters = x509ProtectionParameters;

            return new CustomBinding(messageSecurity, httpTransport);
        }

        /// <summary>
        /// Cria um Binding sob o protocolo TCP para o Bvmf
        /// </summary>
        /// <returns>CustomBinding</returns>
        public static Binding CreateTcpTransportBvmfBinding()
        {
            //Definição do protocolo de transporte do Binding
            TcpTransportBindingElement tcpTransport = new TcpTransportBindingElement();

            tcpTransport.MaxBufferPoolSize = 122880;
            tcpTransport.MaxBufferSize = Int32.MaxValue;
            tcpTransport.MaxReceivedMessageSize = Int32.MaxValue;
            tcpTransport.ConnectionBufferSize = 122880;

            //Definição da segurança da mensagem.
            SymmetricSecurityBindingElement messageSecurity = new SymmetricSecurityBindingElement();

            Binding issuerBinding = null;
            EndpointAddress issuerEndpoint = null;

            GetIssuerConfiguration(out issuerEndpoint, out issuerBinding);

            //Criação do Issued Token
            messageSecurity.ProtectionTokenParameters =
                new SecureConversationSecurityTokenParameters(SecurityBindingElement.CreateIssuedTokenBindingElement(
                            new IssuedSecurityTokenParameters(Constants.SamlTokenType,
                                issuerEndpoint, issuerBinding)));

            //Definição da Formatação da mensagem
            BinaryMessageEncodingBindingElement messageEncoding = new BinaryMessageEncodingBindingElement();

            //Criação do CustomBinding
            return new CustomBinding(messageSecurity, messageEncoding, tcpTransport);
        }

        /// <summary>
        /// Cria um Binding sob o protocolo TCP para o Bvmf
        /// </summary>
        /// <returns>CustomBinding</returns>
        public static Binding CreateNamedPipeTransportBvmfBinding()
        {
            //Definição do protocolo de transporte do Binding
            NamedPipeTransportBindingElement namePipeTransport = new NamedPipeTransportBindingElement();

            namePipeTransport.MaxBufferPoolSize = 122880;
            namePipeTransport.MaxBufferSize = Int32.MaxValue;
            namePipeTransport.MaxReceivedMessageSize = Int32.MaxValue;
            namePipeTransport.ConnectionBufferSize = 122880;

            //Definição da segurança da mensagem.
            SymmetricSecurityBindingElement messageSecurity = new SymmetricSecurityBindingElement();

            Binding issuerBinding = null;
            EndpointAddress issuerEndpoint = null;

            GetIssuerConfiguration(out issuerEndpoint, out issuerBinding);

            //Criação do Issued Token
            messageSecurity.ProtectionTokenParameters =
                new SecureConversationSecurityTokenParameters(SecurityBindingElement.CreateIssuedTokenBindingElement(
                            new IssuedSecurityTokenParameters(Constants.SamlTokenType,
                            issuerEndpoint, issuerBinding)));

            //Definição da Formatação da mensagem
            BinaryMessageEncodingBindingElement messageEncoding = new BinaryMessageEncodingBindingElement();

            return new CustomBinding(messageSecurity, messageEncoding, namePipeTransport);
        }

        /// <summary>
        /// Adicionar o Binding do STS dentro do Binding de serviço
        /// </summary>
        public static void AddIssuerBinding(Binding bindingRef)
        {
            CustomBinding cb = bindingRef as CustomBinding;
            SecurityBindingElement messageSecurity = cb.Elements.Find<SecurityBindingElement>();

            if (messageSecurity != null)
            {
                Binding issuerBinding = null;
                System.ServiceModel.EndpointAddress issuerEndpoint = null;

                GetIssuerConfiguration(out issuerEndpoint, out issuerBinding);

                if (messageSecurity is SymmetricSecurityBindingElement)
                {
                    IssuedSecurityTokenParameters parameters = new IssuedSecurityTokenParameters(Constants.SamlTokenType, issuerEndpoint, issuerBinding);
                    ((SymmetricSecurityBindingElement)messageSecurity).ProtectionTokenParameters = new SecureConversationSecurityTokenParameters(SecurityBindingElement.CreateIssuedTokenBindingElement(parameters));
                }
                else if (messageSecurity is TransportSecurityBindingElement &&
                    messageSecurity.EndpointSupportingTokenParameters.Endorsing.Count > 0 &&
                    messageSecurity.EndpointSupportingTokenParameters.Endorsing[0] is SecureConversationSecurityTokenParameters)
                {
                    SecureConversationSecurityTokenParameters p =
                        messageSecurity.EndpointSupportingTokenParameters.Endorsing[0] as SecureConversationSecurityTokenParameters;
                    IssuedSecurityTokenParameters parameters = p.BootstrapSecurityBindingElement.EndpointSupportingTokenParameters.Endorsing[0] as IssuedSecurityTokenParameters;
                    if (parameters != null)
                    {
                        parameters.IssuerAddress = issuerEndpoint;
                        parameters.IssuerBinding = issuerBinding;
                    }
                }
            }
        }

        #endregion

        #region Issuer Transport

        /// <summary>
        /// Cria um Binding para o Issuer (STS) Bvmf sob o protocolo TCP
        /// </summary>
        /// <returns>CustomBinding</returns>
        private static Binding CreateTcpTransportIssuerBinding()
        {
            TcpTransportBindingElement tcpTransport = new TcpTransportBindingElement();

            SymmetricSecurityBindingElement messageSecurity = new SymmetricSecurityBindingElement();

            //TODO: Duvida - Precisa passar um parameter
            //messageSecurity.EndpointSupportingTokenParameters.SignedEncrypted.Add(new BvmfTokenParameters());
            messageSecurity.EndpointSupportingTokenParameters.SignedEncrypted.Add(new TokenParameters());

            X509SecurityTokenParameters x509ProtectionParameters = new X509SecurityTokenParameters();
            x509ProtectionParameters.InclusionMode = SecurityTokenInclusionMode.Never;
            messageSecurity.ProtectionTokenParameters = x509ProtectionParameters;

            SecurityBindingElement security = SecurityBindingElement.CreateSecureConversationBindingElement(messageSecurity);

            //Definição da Formatação da mensagem
            BinaryMessageEncodingBindingElement messageEncoding = new BinaryMessageEncodingBindingElement();

            return new CustomBinding(security, messageEncoding, tcpTransport);
        }

        /// <summary>
        /// Cria um Binding para o Issuer (STS) Bvmf sob o protocolo Http
        /// </summary>
        /// <returns>CustomBinding</returns>
        public static Binding CreateHttpTransportIssuerBinding()
        {
            HttpTransportBindingElement httpTransport = new HttpTransportBindingElement();

            httpTransport.MaxBufferPoolSize = 122880;
            httpTransport.MaxBufferSize = Int32.MaxValue;
            httpTransport.MaxReceivedMessageSize = Int32.MaxValue;

            SymmetricSecurityBindingElement messageSecurity = new SymmetricSecurityBindingElement();

            //TODO: Duvida - Precisa  passar um parameter
            //messageSecurity.EndpointSupportingTokenParameters.SignedEncrypted.Add(new BvmfTokenParameters());
            messageSecurity.EndpointSupportingTokenParameters.SignedEncrypted.Add(new TokenParameters());

            X509SecurityTokenParameters x509ProtectionParameters = new X509SecurityTokenParameters();
            x509ProtectionParameters.InclusionMode = SecurityTokenInclusionMode.Never;
            messageSecurity.ProtectionTokenParameters = x509ProtectionParameters;
            return new CustomBinding(messageSecurity, httpTransport);
        }

        /// <summary>
        /// Retorna o endereço do Emissor de Token configurado.
        /// </summary>
        /// <returns></returns>
        private static EndpointAddress RetrieveIssuerEndpoint()
        {
            ApplicationConfiguration config = ApplicationConfiguration.GetCurrent();

            string endpointAddress = config.GetSection<ServicesConfigurationSection>(SERVICES_CONFIGURATION_SECTION_NAME).IssuerAddress;
            if (String.IsNullOrEmpty(endpointAddress))
                throw new ArgumentNullException("The issuer address configuration is not set");

            string certificate = config.GetSection<CertificateConfigurationSection>(CERTIFICATE_CONFIGURATION_SECTION_NAME).STSAuthorityCertificate.Name;

            return new EndpointAddress(new Uri(endpointAddress), new DnsEndpointIdentity(certificate));

        }

        /// <summary>
        /// Monta o endpoint e o Binding Configurado para o Issuer.
        /// </summary>
        /// <param name="endpoint"></param>
        /// <param name="binding"></param>
        private static void GetIssuerConfiguration(out EndpointAddress endpoint, out Binding binding)
        {
            endpoint = RetrieveIssuerEndpoint();

            if (endpoint.Uri.AbsoluteUri.ToUpper().StartsWith("HTTP"))
                binding = CreateHttpTransportIssuerBinding();
            else
                binding = CreateTcpTransportIssuerBinding();
        }

        #endregion
    }
}
